﻿using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using System.Text;
using CodeProject.Data.Entity;
using InvoiceSample;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestProjectCS.Attributes;

namespace TestProjectCS
{
	[TestClass]
	public class SaveInvoiceTests : BaseInvoiceTests
	{
		/// <summary>
		/// Test the creation and saving of a new invoice.
		/// </summary>
		[TestMethod]
		[RollingBackTransaction]
		public void CreateNewEmptyInvoiceTest()
		{
			Invoice newinv;

			// Create invoice:
			newinv = new Invoice();
			newinv.Customer = this.Customers[4];
			newinv.InvoiceDate = DateTime.Now;
			newinv.StateCode = "NEW";
			newinv.Comments = "This is a new empty invoice.";

			// Save invoice:
			newinv = this.SaveInvoice(newinv);
			this.ShowInvoice(newinv, "Saved invoice:");

			Assert.AreEqual("NEW", newinv.StateCode);
			Assert.AreEqual("This is a new empty invoice.", newinv.Comments);
			Assert.AreEqual(0, newinv.Lines.Count);
			Assert.AreEqual(0m, newinv.TotalPrice);

			// Retrieve invoice from store and check total price:
			Assert.AreEqual(0m, this.GetDetachedInvoice(newinv.Identifier).TotalPrice);
		}

		/// <summary>
		/// Test the creation and saving of a new invoice.
		/// </summary>
		[TestMethod]
		[RollingBackTransaction]
		public void CreateNewFilledInvoiceTest()
		{
			Invoice newinv;
			InvoiceLine line;

			// Create invoice:
			newinv = new Invoice();
			newinv.Customer = this.Customers[4];
			newinv.InvoiceDate = DateTime.Now;
			newinv.StateCode = "NEW";
			newinv.Comments = "This is a new invoice.";

			// Add an invoiceline:
			line = new InvoiceLine();
			line.Product = this.Products[1];
			line.Quantity = 3;
			line.UnitPrice = line.Product.SalePrice;
			newinv.Lines.Add(line);

			// Add another line:
			line = new InvoiceLine();
			line.Product = this.Products[4];
			line.Quantity = 1;
			line.UnitPrice = line.Product.SalePrice;
			newinv.Lines.Add(line);

			// Save invoice:
			newinv = this.SaveInvoice(newinv);
			this.ShowInvoice(newinv, "Saved invoice:");

			Assert.AreEqual("NEW", newinv.StateCode);
			Assert.AreEqual("This is a new invoice.", newinv.Comments);
			Assert.AreEqual(2, newinv.Lines.Count);
			Assert.AreEqual(2359.99m, newinv.TotalPrice);

			// Retrieve invoice from store and check total price:
			Assert.AreEqual(2359.99m, this.GetDetachedInvoice(newinv.Identifier).TotalPrice);
		}

		/// <summary>
		/// Test the editing and addition of lines on an invoice to be saved.
		/// </summary>
		[TestMethod]
		[RollingBackTransaction]
		public void ExtendInvoiceTest()
		{
			Invoice inv = this.GetDetachedInvoice(5);
			InvoiceLine line;

			this.ShowInvoice(inv, "Original invoice:");

			Assert.AreEqual(1329.98m, inv.TotalPrice);

			// Update invoice:
			inv.Customer = this.Customers[5];
			inv.InvoiceDate = inv.InvoiceDate.AddDays(3);

			// Update existing line:
			line = inv.Lines.Where(p => p.ID == 8).First();
			line.Product = this.Products[5];
			line.UnitPrice = line.Product.SalePrice;

			// Add an extra lines:
			line = new InvoiceLine();
			line.Quantity = 5;
			line.Product = this.Products[6];
			line.UnitPrice = line.Product.SalePrice;
			inv.Lines.Add(line);

			// Add another extra line:
			line = new InvoiceLine();
			line.Quantity = 1;
			line.Product = this.Products[7];
			line.UnitPrice = line.Product.SalePrice - 2.5m;
			inv.Lines.Add(line);

			Assert.AreEqual(340.93m, inv.TotalPrice);

			// Save invoice:
			inv = this.SaveInvoice(inv);
			this.ShowInvoice(inv, "Saved invoice:");

			// Retrieve invoice from store and check total price:
			Assert.AreEqual(340.93m, this.GetDetachedInvoice(5).TotalPrice);
		}

		/// <summary>
		/// Test the editing and removal of lines on an invoice to be saved.
		/// </summary>
		[TestMethod]
		[RollingBackTransaction]
		public void ReduceInvoiceTest()
		{
			Invoice inv = this.GetDetachedInvoice(3);
			InvoiceLine line;

			this.ShowInvoice(inv, "Original invoice:");

			Assert.AreEqual(757.84m, inv.TotalPrice);

			// Update existing line:
			line = inv.Lines.Where(p => p.ID == 6).First();
			line.UnitPrice = 200m;
			line.Quantity = 4;

			Assert.AreEqual(804.99m, inv.TotalPrice);

			// Remove line:
			line = inv.Lines.Where(p => p.ID == 4).First();
			inv.Lines.Remove(line);

			// Remove line:
			line = inv.Lines.Where(p => p.ID == 5).First();
			inv.Lines.Remove(line);

			Assert.AreEqual(800m, inv.TotalPrice);

			// Save invoice:
			inv = this.SaveInvoice(inv);
			this.ShowInvoice(inv, "Saved invoice:");

			// Retrieve invoice from store and check total price:
			Assert.AreEqual(800m, this.GetDetachedInvoice(3).TotalPrice);
		}

		/// <summary>
		/// Tests the edition of an invoice without actually updating the products.
		/// </summary>
		[TestMethod]
		[RollingBackTransaction]
		public void UpdateInvoiceNotProductsTest()
		{
			string prod2LabelBefore = this.Products[2].Label;
			decimal? prod2PriceBefore = this.Products[2].SalePrice;

			Invoice inv = this.GetDetachedInvoice(5);
			InvoiceLine line;

			this.ShowInvoice(inv, "Original invoice:");

			Assert.AreEqual(1329.98m, inv.TotalPrice);

			// Edit all product of order:
			foreach (InvoiceLine item in inv.Lines)
			{
				item.Product.Label = item.Product.Label + " (updated)";
				item.Product.SalePrice += 100m;
			}

			// Update invoice:
			inv.Customer = this.Customers[5];
			inv.InvoiceDate = inv.InvoiceDate.AddDays(3);

			// Update existing line:
			line = inv.Lines.Where(p => p.ID == 8).First();
			line.Quantity++;
			line.UnitPrice = line.Product.SalePrice;

			Assert.AreEqual(2169.98m, inv.TotalPrice);

			// Save invoice:
			inv = this.SaveInvoice(inv);
			this.ShowInvoice(inv, "Saved invoice:");

			// Clear cached data:
			this.ResetCaches();

			// Retrieve invoice from store and check total price:
			Assert.AreEqual(2169.98m, inv.TotalPrice);

			// Verify product information was NOT updated:
			Assert.AreEqual(prod2LabelBefore, this.Products[2].Label);
			Assert.AreEqual(prod2PriceBefore, this.Products[2].SalePrice);
		}

		[TestMethod]
		[RollingBackTransaction]
		public void SmallUpdateOnLargeInvoiceTest()
		{
			Invoice inv;

			// Create a large invoice:
			inv = GetDetachedInvoice(3);
			for (int i = 0; i < 20; i++)
			{
				InvoiceLine line = new InvoiceLine();
				line.Quantity = i;
				line.Product = this.Products[1];
				line.UnitPrice = line.Product.SalePrice;
				inv.Lines.Add(line);
			}
			inv = new SampleInvoiceContext().DetachObjectGraph(this.SaveInvoice(inv));
			this.ShowInvoice(inv, "A large invoice");
			Assert.AreEqual(149907.84m, inv.TotalPrice);

			// Update one single line of the invoice:
			inv.Lines.Single(p => p.Quantity == 12).UnitPrice *= 0.50m;

			// Save the invoice:
			inv = this.SaveInvoice(inv);

			// Verify saved invoice:
			this.ShowInvoice(inv, "Saved invoice");
			Assert.AreEqual(145197.84m, inv.TotalPrice);
		}

		#region SaveInvoice method

		/// <summary>
		/// Save invoice implementation using AttachObjectGraph.
		/// </summary>
		/// <param name="invoice">The (detached) invoice to be attached and saved.</param>
		/// <returns>The attached version of the invoice.</returns>
		Invoice SaveInvoice(Invoice invoice)
		{
			using (var context = new SampleInvoiceContext())
			{
				// Attach the invoice:
				Invoice attachedInvoice = context.AttachObjectGraph(
					invoice,
					p => p.Customer,
					p => p.Lines.First().Product.WithoutUpdate()
				);

				// Save changes:
				context.SaveChanges();

				// Return the saved instance:
				return attachedInvoice;
			}
		}

		#endregion
	}
}
